using System;
using System.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace HeroldIT.Dnn.Modules
{
    /// <summary>
    /// Provides helper methods for processing images.
    /// </summary>
    public static class ImageHelper
    {
        /// <summary>
        /// Calculates the scaling factor required for the original dimensions to fit into the target dimensions.
        /// </summary>
        /// <param name="origWidth">The original width.</param>
        /// <param name="origHeight">The original height.</param>
        /// <param name="targetWidth">The target width.</param>
        /// <param name="targetHeight">The target height.</param>
        /// <returns>The scaling factor required for the original dimensions to fit into the target dimensions.</returns>
        public static float GetScaleFactor(int origWidth, int origHeight, int targetWidth, int targetHeight)
        {
            float factor = 1.0f;
            if (targetWidth > 0)
            {
                factor = Math.Min(factor, targetWidth / (float)origWidth);
            }
            if (targetHeight > 0)
            {
                factor = Math.Min(factor, targetHeight / (float) origHeight);
            }
            return factor;
        }

        /// <summary>
        /// Scales an image proportionally to fit into a new image with the specified size.
        /// Unused parts of the scaled image are filled with white.
        /// </summary>
        /// <param name="image">the image to scale</param>
        /// <param name="width">the maximum width for the resulting image</param>
        /// <param name="height">the maximum height for the resulting image</param>
        /// <returns>the scaled image</returns>
        public static Image GetScaledImage(this Image image, int width, int height)
        {
            return GetScaledImage(image, width, height, Brushes.White);
        }

        /// <summary>
        /// Scales an image proportionally to fit into a new image with the specified size.
        /// </summary>
        /// <param name="image">the image to scale</param>
        /// <param name="width">the maximum width for the resulting image</param>
        /// <param name="height">the maximum height for the resulting image</param>
        /// <param name="backgroundColor">the background color with which to fill unused parts of the image</param>
        /// <returns>an image with the given proportions containing the scaled image</returns>
        public static Image GetScaledImage(this Image image, int width, int height, Brush backgroundColor)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            float factor = GetScaleFactor(image.Width, image.Height, width, height);

            Bitmap bm = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(bm);
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            g.FillRectangle(backgroundColor, -0.5f, -0.5f, width, height);
            g.DrawImage(image, ((width - image.Width * factor) / 2) - 0.5f, ((height - image.Height * factor) / 2) - 0.5f, image.Width * factor + 0.5f, image.Height * factor + 0.5f);

            return bm;
        }

        /// <summary>
        /// Scales an image proportionally to fit into a new image with the specified maximum sizes.
        /// </summary>
        /// <param name="image">the image to scale</param>
        /// <param name="maxWidth">the maximum width for the resulting image</param>
        /// <param name="maxHeight">the maximum height for the resulting image</param>
        /// <returns>the scaled image</returns>
        public static Image GetProportionallyScaledImage(this Image image, int maxWidth, int maxHeight)
        {
            return GetProportionallyScaledImage(image, maxWidth, maxHeight, Color.Empty);
        }

        /// <summary>
        /// Scales an image proportionally to fit into a new image with the specified maximum sizes.
        /// </summary>
        /// <param name="image">the image to scale</param>
        /// <param name="maxWidth">the maximum width for the resulting image</param>
        /// <param name="maxHeight">the maximum height for the resulting image</param>
        /// <param name="matte">the matte color for the resulting image</param>
        /// <returns>the scaled image</returns>
        public static Image GetProportionallyScaledImage(this Image image, int maxWidth, int maxHeight, Color matte)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            float factor = GetScaleFactor(image.Width, image.Height, maxWidth, maxHeight);

            if (factor >= 1)
                return CopyImage(image);

            Bitmap bm = new Bitmap((int)Math.Floor(image.Width * factor), (int)Math.Floor(image.Height * factor));
            Graphics g = Graphics.FromImage(bm);
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            if (matte != Color.Empty)
            {
                g.FillRectangle(new SolidBrush(matte), -0.5f, -0.5f, bm.Width, bm.Height);
            }
            g.DrawImage(image, -0.5f, -0.5f, (int)Math.Floor(image.Width * factor) + 0.5f, (int)Math.Floor(image.Height * factor) + 0.5f);

            return bm;
        }

        /// <summary>
        /// Copies an image into a new memory-based bitmap.
        /// </summary>
        /// <param name="image">the image to copy</param>
        /// <returns>a memory-based copy of the image</returns>
        public static Image CopyImage(this Image image)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            Image copy = new Bitmap(image.Width, image.Height);

            Graphics g = Graphics.FromImage(copy);
            g.DrawImage(image, 0, 0, copy.Width, copy.Height);

            return copy;
        }

        /// <summary>
        /// Reads an image from a file and returns a memory-based copy, releasing the file handle.
        /// </summary>
        /// <param name="filename">The filename of the image file</param>
        /// <returns>a memory-based copy of the read image</returns>
        public static Image FromFile(string filename)
        {
            using (Image image = Image.FromFile(filename))
            {
                return CopyImage(image);
            }
        }

        /// <summary>
        /// Reads an image from a file and returns a memory-based copy, releasing the file handle.
        /// </summary>
        /// <param name="filename">The filename of the image file</param>
        /// <param name="useEmbeddedColorManagement">Set to true to use color management information embedded in the file; otherwise, false.</param>
        /// <returns>a memory-based copy of the read image</returns>
        public static Image FromFile(string filename, bool useEmbeddedColorManagement)
        {
            using (Image image = Image.FromFile(filename, useEmbeddedColorManagement))
            {
                return CopyImage(image);
            }
        }

        /// <summary>
        /// Creates an image from an array of bytes; typically used for creating images from binary objects stored in a database.
        /// </summary>
        /// <param name="bytes">The byte array containing the image data</param>
        /// <returns>the image</returns>
        public static Image FromByteArray(byte[] bytes)
        {
            using (MemoryStream stream = new MemoryStream(bytes))
            {
                using (Image image = Image.FromStream(stream))
                {
                    return CopyImage(image);
                }
            }
        }

        /// <summary>
        /// Creates a byte array from an image; typically used for creating binary data for storing in a database.
        /// </summary>
        /// <param name="image">the image to convert</param>
        /// <param name="format">the format in which to store the image</param>
        /// <returns>the byte array containing the image data</returns>
        public static byte[] ToByteArray(this Image image, ImageFormat format)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            using (MemoryStream stream = new MemoryStream())
            {
                image.Save(stream, format);
                byte[] buffer = stream.GetBuffer();
                byte[] imageBytes = new byte[stream.Length];
                Array.Copy(buffer, imageBytes, stream.Length);
                return imageBytes;
            }
        }

        /// <summary>
        /// Creates a byte array from an image; typically used for creating binary data for storing in a database.
        /// </summary>
        /// <param name="image">the image to convert</param>
        /// <returns>the byte array containing the image data</returns>
        public static byte[] ToByteArray(this Image image)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            return image.ToByteArray(ImageFormat.Jpeg);
        }

        /// <summary>
        /// Stores an <paramref name="image"/> into a jpeg byte array with the specified <paramref name="quality"/>.
        /// </summary>
        /// <param name="image">The image to store as jpeg</param>
        /// <param name="quality">The quality parameter for jpeg compression</param>
        /// <returns>A byte array containing the jpeg image data.</returns>
        public static byte[] ToJpegByteArray(this Image image, long quality)
        {
            ImageCodecInfo jpegCodec = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.FormatID.Equals(ImageFormat.Jpeg.Guid));
            if (null == jpegCodec)
                throw new InvalidOperationException("Jpeg codec is not supported on this system.");

            var eps = new EncoderParameters(1)
            {
                Param = new[]
                                                  {
                                                      new EncoderParameter(Encoder.Quality, quality)
                                                  }
            };

            using (var imageStream = new MemoryStream())
            {
                image.Save(imageStream, jpegCodec, eps);
                return imageStream.ToArray();
            }
        }

        /// <summary>
        /// Tiles the specified <paramref name="watermarkImage"/> across the <paramref name="image"/>.
        /// </summary>
        /// <param name="image">The image to add the watermark to.</param>
        /// <param name="watermarkImage">The watermark image to add to the image.</param>
        public static void WatermarkWithTiledImage(this Image image, Image watermarkImage)
        {
            if (null == image)
                throw new ArgumentNullException("image");
            if (null == watermarkImage)
                throw new ArgumentNullException("watermarkImage");

            using (Graphics g = Graphics.FromImage(image))
            {
                g.CompositingMode = CompositingMode.SourceOver;
                g.CompositingQuality = CompositingQuality.HighQuality;
                g.InterpolationMode = InterpolationMode.NearestNeighbor;
                g.SmoothingMode = SmoothingMode.None;

                int y = 0;
                do
                {
                    int x = 0;
                    do
                    {
                        g.DrawImageUnscaled(watermarkImage, x, y);
                        x += watermarkImage.Width;
                    } while (x < image.Width);
                    y += watermarkImage.Height;
                } while (y < image.Height);
            }
        }
    }
}